package net.quanter.shield.dubbo.interceptor;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import net.quanter.shield.common.dto.context.InvocationContextDTO;
import net.quanter.shield.common.dto.context.InvocationDTO;
import net.quanter.shield.common.dto.result.ResultDTO;
import net.quanter.shield.common.enums.context.InvocationType;
import net.quanter.shield.common.enums.http.HttpCode;
import net.quanter.shield.utils.bean.BeanUtils;
import net.quanter.shield.utils.date.DateFormatter;
import net.quanter.shield.utils.invocation.InvocationUtil;
import net.quanter.shield.utils.string.StringComplexity;
import net.quanter.shield.utils.string.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

/***
 * 这个也是调用链的起始方法的入口函数
 * 这个类的作用是拦截springboot项目中所有带有InvocationSupport注解的类或者方法
 * 然后在threadLocal里配置调用链参数
 * 再最后方法执行完后，从threadLocal里获得完整的调用链并填充到ResultDTO的x属性里
 * 响应到调用方
 * 从而实现一次完整的请求调用链
 *
 * 使用方法
 * 1.在需要被拦截的类或者方法上加注解net.quanter.shield.springboot.annotations.InvocationSupport
 * 2.在http请求的header里加上DEBBO_DEBUG
 * 3.请求的方法返回值使用ResultDTO类型
 * 附加
 * 1.如果在http请求里加上trace_id，即可使用http里传入的trace_id作为debug的id，如果不加，则生成一个随机的trace_id
 *
 * created on 2020-10-11
 * @author 王老实
 * @see net.quanter.shield.springboot.annotations.InvocationSupport
 *
 */
@Aspect
@Component
public class DubboInvocationInterceptor {

    private static final String DEBBO_DEBUG = "DEBBO_DEBUG";
    private static final String TRACD_ID = "trace_id";

    @Pointcut("@annotation(net.quanter.shield.springboot.annotations.InvocationSupport)")
    private void pointcut() {
    }

    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint invocation) throws Throwable {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return invocation.proceed();
        }
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        if (request == null) {
            return invocation.proceed();
        }
        String dubboDebug = request.getHeader(DEBBO_DEBUG);
        long begin = System.currentTimeMillis();
        InvocationDTO invocationDTO = null;
        InvocationContextDTO contextDTO = null;
        if (dubboDebug != null) {
            invocationDTO = new InvocationDTO();
            contextDTO = new InvocationContextDTO();
            String trace_id = request.getHeader(TRACD_ID);
            String traceId = StringUtils.isEmpty(trace_id) ?
                    DateFormatter.formatNow(DateFormatter.DATE_FORMAT_FULL_SIMPLE) + StringUtils.genString(10, StringComplexity.LEVEL3)
                    : trace_id.trim();
            contextDTO.setTraceId(traceId);
            contextDTO.setIndex(0);
            invocationDTO.setIndex(contextDTO.getIndex());
            invocationDTO.setMethodName(invocation.getSignature().getName());
            invocationDTO.setServiceName(invocation.getTarget().getClass().getName());
            invocationDTO.setArgs(BeanUtils.clones(invocation.getArgs()));
            invocationDTO.setType(InvocationType.SPRINGBOOT);
            InvocationUtil.begin(contextDTO);
        }
        if (dubboDebug == null) {
            return invocation.proceed();
        } else {
            Object result = null;
            try {
                result = invocation.proceed();
            } catch (Throwable e) {
                result = ResultDTO.failure(e.getMessage(), HttpCode.INTERNAL_SERVER_ERROR.code);
                invocationDTO.setE(e);
            }
            List<InvocationDTO> invocationChain = InvocationUtil.getInvocationChain();
            invocationDTO.setRt(System.currentTimeMillis() - begin);
            invocationDTO.setChain(invocationChain);
            if (result != null && result instanceof ResultDTO) {
                invocationDTO.setResult(BeanUtils.clone(result));
                ResultDTO resultDTO = (ResultDTO) result;
                resultDTO.putX("chain", invocationDTO);
                resultDTO.putX("traceId", contextDTO.getTraceId());
            }
            InvocationUtil.end();
            return result;
        }
    }
}
